// Constructs an application object
function Application(applicationPath)
{
    this.applicationPath = applicationPath;
    this.executable      = this.getExecutable();
    this.file            = null;
}

// Creates a source file
Application.prototype.createSourceFile = function(temporaryDirectory, uri)
{
    var sourceFile = null;

    // If the URI has a file scheme
    if(uri.scheme == "file")
    {
        var fileProtocolHandler = Components.classes["@mozilla.org/network/protocol;1?name=file"].createInstance(Components.interfaces.nsIFileProtocolHandler);

        sourceFile = fileProtocolHandler.getFileFromURLSpec(uri.spec);
    }

    // If the source file is not set
    if(!sourceFile)
    {
        var fileExtension = "html";
        var fileName      = uri.host;
        var url           = Components.classes["@mozilla.org/network/standard-url;1"].createInstance(Components.interfaces.nsIURL);

        sourceFile = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
        url.spec   = uri.spec;

        // If the URL has a file extension
        if(url.fileExtension)
        {
            fileExtension = url.fileExtension;
        }

        temporaryDirectory.append("-" + fileName + "-" + new Date().getTime() + "." + fileExtension);
        sourceFile.initWithPath(temporaryDirectory.path);
    }

    return sourceFile;
}

// Returns an executable for the application
Application.prototype.getExecutable = function()
{
    var executable = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);

    executable.initWithPath(this.applicationPath);

    return executable;
}

// Returns the post data
Application.prototype.getPostData = function()
{
    // Try to get the post data
    try
    {
        var sessionHistory = getWebNavigation().sessionHistory;
        var entry          = sessionHistory.getEntryAtIndex(sessionHistory.index, false).QueryInterface(Components.interfaces.nsISHEntry);

        return entry.postData;
    }
    catch(exception)
    {
        return null;
    }
}

// Launch the application with the given file
Application.prototype.launchWithFile = function()
{
    var mimeService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"].getService(Components.interfaces.nsIMIMEService);
    var mimeInfo    = mimeService.getFromTypeAndExtension(this.file.path, "");

    mimeInfo.alwaysAskBeforeHandling     = false;
    mimeInfo.preferredAction             = Components.interfaces.nsIMIMEInfo.useHelperApp;
    mimeInfo.preferredApplicationHandler = this.executable;

    mimeInfo.launchWithFile(this.file);
}

// Launch the application with the source from the given URI
Application.prototype.launchWithSource = function(uri)
{
    var stringBundle       = document.getElementById("-string-bundle");
    var temporaryDirectory = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties).get("TmpD", Components.interfaces.nsIFile);

    // If the temporary directory exists, is a directory and is writable
    if(temporaryDirectory.exists() && temporaryDirectory.isDirectory() && temporaryDirectory.isWritable())
    {
        // If the executable exists
        if(this.executable.exists())
        {
            this.file = this.createSourceFile(temporaryDirectory, uri);

            if(uri.scheme == "file")
            {
                this.launchWithFile();
            }
            else
            {
                var webBrowserPersistInterface = Components.interfaces.nsIWebBrowserPersist;
                var webBrowserPersist          = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"].createInstance(webBrowserPersistInterface);

                webBrowserPersist.persistFlags     = webBrowserPersistInterface.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION | webBrowserPersistInterface.PERSIST_FLAGS_FROM_CACHE | webBrowserPersistInterface.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
                webBrowserPersist.progressListener = this;

                webBrowserPersist.saveURI(uri, null, uri, this.getPostData(), null, this.file);
            }
        }
        else
        {
            _error(stringBundle.getFormattedString("_launchApplicationFailed", [this.applicationPath]));
        }
    }
    else
    {
        _error(stringBundle.getFormattedString("_tempDirectoryFailed", [temporaryDirectory.path]));
    }
}

// Launch the application with the given URL
Application.prototype.launchWithURL = function(url)
{
    // If the executable exists, is a file and is executable
    if(this.executable.exists() && this.executable.isFile() && this.executable.isExecutable())
    {
        var process          = Components.classes["@mozilla.org/process/util;1"].createInstance(Components.interfaces.nsIProcess);
        var processArguments = new Array(url);

        process.init(this.executable);
        process.run(false, processArguments, processArguments.length);
    }
    else
    {
        _error(document.getElementById("-string-bundle").getFormattedString("_launchApplicationFailed", [this.applicationPath]));
    }
}

// Called when the progress state changes
Application.prototype.onStateChange = function(webProgress, request, stateFlags, status)
{
    // If the progress has stopped
    if(stateFlags & Components.interfaces.nsIWebProgressListener.STATE_STOP)
    {
        this.launchWithFile();
    }
}

// Indicates the interfaces this object supports
Application.prototype.QueryInterface = function(id)
{
    // If the query is for a supported interface
    if(id.equals(Components.interfaces.nsISupports) || id.equals(Components.interfaces.nsIWebProgressListener))
    {
        return this;
    }

    throw Components.results.NS_NOINTERFACE;
}

// Dummy methods requiring implementations
Application.prototype.onLocationChange = function(webProgress, request, location) {}
Application.prototype.onProgressChange = function(webProgress, request, currentSelfProgress, maximumSelfProgress, currentTotalProgress, maximumTotalProgress) {}
Application.prototype.onSecurityChange = function(webProgress, request, state) {}
Application.prototype.onStatusChange   = function(webProgress, request, status, message) {}
